//==================================================================================================
// Name: CREBreakableGlassBuffer
// Desc: Breakable glass geometry buffer management
// Author: Chris Bunner
//==================================================================================================

// Includes
#include "StdAfx.h"
#include "D3DREBreakableGlassBuffer.h"

#include "DriverD3D.h"
#include "VertexFormats.h"

// Shared buffer-type constants
#define NUM_BUFFER_SLOTS		GLASSCFG_MAX_NUM_ACTIVE_GLASS
#define LOOSE_MEM_SIZE			3

#define MAX_NUM_LOOSE_PLANE_VERTS		(GLASSCFG_MAX_NUM_PLANE_VERTS >> LOOSE_MEM_SIZE)
#define MAX_NUM_LOOSE_CRACK_VERTS		(GLASSCFG_MAX_NUM_CRACK_VERTS >> LOOSE_MEM_SIZE)
#define MAX_NUM_LOOSE_PLANE_INDS		(GLASSCFG_MAX_NUM_PLANE_INDS >> LOOSE_MEM_SIZE)
#define MAX_NUM_LOOSE_CRACK_INDS		(GLASSCFG_MAX_NUM_CRACK_INDS >> LOOSE_MEM_SIZE)

// Unique buffer-type constants
struct SVertexConst
{
	SVertexConst(const uint32 numPlaneVerts, const uint32 numCrackVerts, const uint32 numPlaneInds, const uint32 numCrackInds)
	: maxNumPlaneVerts(numPlaneVerts)
	,	maxNumCrackVerts(numCrackVerts)
	,	maxNumPlaneInds(numPlaneInds)
	,	maxNumCrackInds(numCrackInds)
	{
		vbStride = numPlaneVerts + numCrackVerts;
		ibStride = numPlaneInds + numCrackInds;
		totalNumVerts = NUM_BUFFER_SLOTS * vbStride;
		totalNumInds = NUM_BUFFER_SLOTS * ibStride;
		
		vbPlaneOffset = 0;
		vbCrackOffset = numPlaneVerts;
		ibPlaneOffset = 0;
		ibCrackOffset = numPlaneInds;
	}

	uint32	maxNumPlaneVerts;
	uint32	maxNumCrackVerts;
	uint32	maxNumPlaneInds;
	uint32	maxNumCrackInds;

	uint32	vbStride;
	uint32	ibStride;
	uint32	totalNumVerts;
	uint32	totalNumInds;

	uint32	vbPlaneOffset;
	uint32	vbCrackOffset;
	uint32	ibPlaneOffset;
	uint32	ibCrackOffset;
};

const SVertexConst s_bufferConsts[CREBreakableGlassBuffer::EBufferType_Num] =
{
	// Solid buffer vertices
	SVertexConst(GLASSCFG_MAX_NUM_PLANE_VERTS, GLASSCFG_MAX_NUM_CRACK_VERTS,
		GLASSCFG_MAX_NUM_PLANE_INDS, GLASSCFG_MAX_NUM_CRACK_INDS),

	// Loose buffer vertices
	SVertexConst(MAX_NUM_LOOSE_PLANE_VERTS + MAX_NUM_LOOSE_CRACK_VERTS, 0,
		MAX_NUM_LOOSE_PLANE_INDS + MAX_NUM_LOOSE_CRACK_INDS, 0)
};

// Singleton
CREBreakableGlassBuffer* CREBreakableGlassBuffer::s_pInstance = NULL;

//--------------------------------------------------------------------------------------------------
// Name: Constructor
// Desc: Pre-allocated the geometry buffers
//--------------------------------------------------------------------------------------------------
CREBreakableGlassBuffer::CREBreakableGlassBuffer()
{
	InitialiseBuffers();
}

//--------------------------------------------------------------------------------------------------
// Name: Destructor
// Desc: Releases all geometry buffers
//--------------------------------------------------------------------------------------------------
CREBreakableGlassBuffer::~CREBreakableGlassBuffer()
{
	ReleaseBuffers();
}

//--------------------------------------------------------------------------------------------------
// Name: RT_Instance
// Desc: Returns (and creates) the static instance
//--------------------------------------------------------------------------------------------------
CREBreakableGlassBuffer& CREBreakableGlassBuffer::RT_Instance()
{
	ASSERT_IS_RENDER_THREAD(gRenDev->m_pRT);

	if (!s_pInstance)
	{
		s_pInstance = new CREBreakableGlassBuffer();
	}

	return *s_pInstance;
}

//--------------------------------------------------------------------------------------------------
// Name: RT_ReleaseInstance
// Desc: Releases the static instance
//--------------------------------------------------------------------------------------------------
void CREBreakableGlassBuffer::RT_ReleaseInstance()
{
	ASSERT_IS_RENDER_THREAD(gRenDev->m_pRT);
	SAFE_DELETE(s_pInstance);
}

//--------------------------------------------------------------------------------------------------
// Name: InitialiseBuffers
// Desc: Buffer creation called from render thread
//--------------------------------------------------------------------------------------------------
void CREBreakableGlassBuffer::InitialiseBuffers()
{			
	for (uint i = 0; i < EBufferType_Num; ++i)
	{
		SBuffer& buffer = m_buffer[i];
		const SVertexConst& bufferConsts = s_bufferConsts[i];

		// Allocate buffers
		buffer.pVertices = gRenDev->m_DevBufMan.CreateVBuffer(bufferConsts.totalNumVerts, eVF_P3F_C4B_T2F, "BreakableGlassPlaneVB");
		buffer.pIndices = gRenDev->m_DevBufMan.CreateIBuffer(bufferConsts.totalNumInds, "BreakableGlassPlaneIB");
		buffer.tangentStreamID = gRenDev->m_DevBufMan.CreateVB(bufferConsts.totalNumVerts * sizeof(SPipTangents));
		
		// Allocate state
		buffer.pLastPlaneIndCount = new uint32[NUM_BUFFER_SLOTS];
		memset(buffer.pLastPlaneIndCount, 0, sizeof(uint32)*NUM_BUFFER_SLOTS);

		buffer.pLastCrackIndCount = new uint32[NUM_BUFFER_SLOTS];
		memset(buffer.pLastCrackIndCount, 0, sizeof(uint32)*NUM_BUFFER_SLOTS);

		// Reset ID counter just to be safe
		buffer.nextId = NUM_BUFFER_SLOTS;
	}
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: ReleaseBuffers
// Desc: Buffer release called from render thread
//--------------------------------------------------------------------------------------------------
void CREBreakableGlassBuffer::ReleaseBuffers()
{
	for (uint i = 0; i < EBufferType_Num; ++i)
	{
		// Release buffers
		if (m_buffer[i].pVertices)
		{
			gRenDev->m_DevBufMan.ReleaseVBuffer(m_buffer[i].pVertices);
			m_buffer[i].pVertices = NULL;
		}

		if (m_buffer[i].pIndices)
		{
			gRenDev->m_DevBufMan.ReleaseIBuffer(m_buffer[i].pIndices);
			m_buffer[i].pIndices = NULL;
		}

		if (m_buffer[i].tangentStreamID != InvalidStream)
		{
			gRenDev->m_DevBufMan.ReleaseVB(m_buffer[i].tangentStreamID);
			m_buffer[i].tangentStreamID = InvalidStream;
		}

		// Free state
		SAFE_DELETE_ARRAY(m_buffer[i].pLastPlaneIndCount);
		SAFE_DELETE_ARRAY(m_buffer[i].pLastCrackIndCount);
	}
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: RT_AcquireBuffer
// Desc: Supplies an id for the next buffer slot
//--------------------------------------------------------------------------------------------------
uint32 CREBreakableGlassBuffer::RT_AcquireBuffer(const bool isFrag)
{
	EBufferType buffType = isFrag ? EBufferType_Loose : EBufferType_Solid;
	SBuffer& buffer = m_buffer[buffType];

	// Reset data	for new user
	++buffer.nextId;

	const uint32 id = buffer.nextId;
	const uint32 cyclicId = buffer.nextId % NUM_BUFFER_SLOTS;
	buffer.pLastPlaneIndCount[cyclicId] = 0;
	buffer.pLastCrackIndCount[cyclicId] = 0;

	// Return id
	return id;
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: RT_IsBufferValid
// Desc: Cyclic buffer ids only valid for last "NUM_BUFFER_SLOTS" requests
//--------------------------------------------------------------------------------------------------
bool CREBreakableGlassBuffer::RT_IsBufferValid(const uint32 id, const bool isFrag) const
{
	const SBuffer& buffer = GetBuffer(isFrag);
	return (id != NoBuffer) && (id > buffer.nextId - NUM_BUFFER_SLOTS) && (id <= buffer.nextId);
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: RT_UpdatePlaneVertexBuffer
// Desc: Updates a plane's vertex buffer
//--------------------------------------------------------------------------------------------------
bool CREBreakableGlassBuffer::RT_UpdatePlaneVertexBuffer(const uint32 id, PodArray<SVF_P3F_C4B_T2F>& vertices, const bool isFrag)
{
	const SVertexConst& bufferConsts = s_bufferConsts[isFrag ? EBufferType_Loose : EBufferType_Solid];

	const uint32 vertCount = min((uint32)vertices.Count(), bufferConsts.maxNumPlaneVerts);
	const uint32 offset = (id % NUM_BUFFER_SLOTS) * bufferConsts.vbStride + bufferConsts.vbPlaneOffset;

	return UpdateVBuffer(id, &vertices[0], vertCount, offset, isFrag);
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: RT_UpdatePlaneIndexBuffer
// Desc: Updates a plane's index buffer
//--------------------------------------------------------------------------------------------------
bool CREBreakableGlassBuffer::RT_UpdatePlaneIndexBuffer(const uint32 id, PodArray<uint16>& indices, const bool isFrag)
{
	const SVertexConst& bufferConsts = s_bufferConsts[isFrag ? EBufferType_Loose : EBufferType_Solid];
	const uint32 indCount = min((uint32)indices.Count(), bufferConsts.maxNumPlaneInds);

	// Apply vertex buffer offset to indices
	const uint32 cylicId = id % NUM_BUFFER_SLOTS;
	const uint32 vertOffset = cylicId * bufferConsts.vbStride + bufferConsts.vbPlaneOffset;
	uint16* pIndices = &indices[0];

	for (uint32 i = 0; i < indCount; ++i)
	{
		pIndices[i] += vertOffset;
	}

	// Update index data
	const SBuffer& buffer = GetBuffer(isFrag);

	if (buffer.pLastPlaneIndCount)
	{
		buffer.pLastPlaneIndCount[cylicId] = indCount;
	}

	const uint32 indOffset = cylicId * bufferConsts.ibStride + bufferConsts.ibPlaneOffset;
	return UpdateIBuffer(id, &indices[0], indCount, indOffset, isFrag);
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: RT_UpdatePlaneTangentBuffer
// Desc: Updates a plane's tangent buffer
//--------------------------------------------------------------------------------------------------
bool CREBreakableGlassBuffer::RT_UpdatePlaneTangentBuffer(const uint32 id, PodArray<SPipTangents>& tangents, const bool isFrag)
{
	const SVertexConst& bufferConsts = s_bufferConsts[isFrag ? EBufferType_Loose : EBufferType_Solid];

	const uint32 tanCount = min((uint32)tangents.Count(), bufferConsts.maxNumPlaneVerts);
	const uint32 offset = (id % NUM_BUFFER_SLOTS) * bufferConsts.vbStride + bufferConsts.vbPlaneOffset;

	return UpdateTBuffer(id, &tangents[0], tanCount, offset, isFrag);
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: RT_UpdateCrackVertexBuffer
// Desc: Updates a crack's vertex buffer
//--------------------------------------------------------------------------------------------------
bool CREBreakableGlassBuffer::RT_UpdateCrackVertexBuffer(const uint32 id, PodArray<SVF_P3F_C4B_T2F>& vertices, const bool isFrag)
{
	CRY_ASSERT_MESSAGE(!isFrag, "Trying to update a glass frag buffer that should be empty");

	const SVertexConst& bufferConsts = s_bufferConsts[isFrag ? EBufferType_Loose : EBufferType_Solid];

	const uint32 vertCount = min((uint32)vertices.Count(), bufferConsts.maxNumCrackVerts);
	const uint32 offset = (id % NUM_BUFFER_SLOTS) * bufferConsts.vbStride + bufferConsts.vbCrackOffset;

	return UpdateVBuffer(id, &vertices[0], vertCount, offset, isFrag);
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: RT_UpdateCrackIndexBuffer
// Desc: Updates a crack's index buffer
//--------------------------------------------------------------------------------------------------
bool CREBreakableGlassBuffer::RT_UpdateCrackIndexBuffer(const uint32 id, PodArray<uint16>& indices, const bool isFrag)
{
	CRY_ASSERT_MESSAGE(!isFrag, "Trying to update a glass frag buffer that should be empty");

	const SVertexConst& bufferConsts = s_bufferConsts[isFrag ? EBufferType_Loose : EBufferType_Solid];
	const uint32 indCount = min((uint32)indices.Count(), bufferConsts.maxNumCrackInds);

	// Apply vertex buffer offset to indices
	const uint32 cylicId = id % NUM_BUFFER_SLOTS;
	const uint32 vertOffset = cylicId * bufferConsts.vbStride + bufferConsts.vbCrackOffset;
	uint16* pIndices = &indices[0];

	for (uint32 i = 0; i < indCount; ++i)
	{
		pIndices[i] += vertOffset;
	}

	// Update index data
	const SBuffer& buffer = GetBuffer(isFrag);

	if (buffer.pLastCrackIndCount)
	{
		buffer.pLastCrackIndCount[cylicId] = indCount;
	}

	const uint32 indOffset = (id % NUM_BUFFER_SLOTS) * bufferConsts.ibStride + bufferConsts.ibCrackOffset;
	return UpdateIBuffer(id, &indices[0], indCount, indOffset, isFrag);
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: RT_UpdatePlaneTangentBuffer
// Desc: Updates a plane's tangent buffer
//--------------------------------------------------------------------------------------------------
bool CREBreakableGlassBuffer::RT_UpdateCrackTangentBuffer(const uint32 id, PodArray<SPipTangents>& tangents, const bool isFrag)
{
	CRY_ASSERT_MESSAGE(!isFrag, "Trying to update a glass frag buffer that should be empty");

	const SVertexConst& bufferConsts = s_bufferConsts[isFrag ? EBufferType_Loose : EBufferType_Solid];

	const uint32 tanCount = min((uint32)tangents.Count(), bufferConsts.maxNumCrackVerts);
	const uint32 offset = (id % NUM_BUFFER_SLOTS) * bufferConsts.vbStride + bufferConsts.vbCrackOffset;

	return UpdateTBuffer(id, &tangents[0], tanCount, offset, isFrag);
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: RT_ClearBuffer
// Desc: Sets the last index count to zero, ensuring the buffer will no longer be drawn
//--------------------------------------------------------------------------------------------------
bool CREBreakableGlassBuffer::RT_ClearBuffer(const uint32 id, const bool isFrag)
{
	const SBuffer& buffer = GetBuffer(isFrag);
	bool success = false;

	if (buffer.pLastCrackIndCount && RT_IsBufferValid(id, isFrag))
	{
		const uint32 cylicId = id % NUM_BUFFER_SLOTS;
		buffer.pLastCrackIndCount[cylicId] = 0;

		success = true;
	}

	return success;
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: RT_DrawPlaneBuffer
// Desc: Draws a plane buffer slot
//--------------------------------------------------------------------------------------------------
bool CREBreakableGlassBuffer::RT_DrawPlaneBuffer(const uint32 id, const bool isFrag)
{
	const SVertexConst& bufferConsts = s_bufferConsts[isFrag ? EBufferType_Loose : EBufferType_Solid];
	const SBuffer& buffer = GetBuffer(isFrag);
	bool success = false;

	if (buffer.pLastPlaneIndCount && RT_IsBufferValid(id, isFrag))
	{	
		const uint32 cyclicId = id % NUM_BUFFER_SLOTS;
		const uint32 offset = cyclicId * bufferConsts.ibStride + bufferConsts.ibPlaneOffset;
		const uint32 count = buffer.pLastPlaneIndCount[cyclicId];

		if (count >= 3)
		{
			DrawBuffer(count, offset, cyclicId, isFrag);
			success = true;
		}
	}

	return success;
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: RT_DrawCrackBuffer
// Desc: Draws a crack buffer slot
//--------------------------------------------------------------------------------------------------
bool CREBreakableGlassBuffer::RT_DrawCrackBuffer(const uint32 id, const bool isFrag)
{
	CRY_ASSERT_MESSAGE(!isFrag, "Trying to draw a glass frag buffer that should be empty");

	const SVertexConst& bufferConsts = s_bufferConsts[isFrag ? EBufferType_Loose : EBufferType_Solid];
	const SBuffer& buffer = GetBuffer(isFrag);
	bool success = false;

	if (buffer.pLastCrackIndCount && RT_IsBufferValid(id, isFrag))
	{	
		const uint32 cyclicId = id % NUM_BUFFER_SLOTS;
		const uint32 offset = cyclicId * bufferConsts.ibStride + bufferConsts.ibCrackOffset;
		const uint32 count = buffer.pLastCrackIndCount[cyclicId];

		if (count >= 3)
		{
			DrawBuffer(count, offset, cyclicId, isFrag);
			success = true;
		}
	}

	return success;
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: UpdateVBuffer
// Desc: Attempts to update an vertex buffer
//--------------------------------------------------------------------------------------------------
bool CREBreakableGlassBuffer::UpdateVBuffer(const uint32 id, SVF_P3F_C4B_T2F* pVertData, const uint32 vertCount, const uint32 offset, const bool isFrag)
{
	const SBuffer& buffer = GetBuffer(isFrag);
	bool success = false;

	if (vertCount > 0 && buffer.pVertices && pVertData && RT_IsBufferValid(id, isFrag))
	{
		const int32 devID = buffer.pVertices->m_VS.m_nDevID;
		CRY_ASSERT_MESSAGE(devID >= 0, "Updating an invalid VBuffer.");

	  const uint32 stride = sizeof(SVF_P3F_C4B_T2F);  
		gRenDev->m_DevBufMan.UpdateVB(devID, pVertData, vertCount * stride, offset * stride);

		success = true;
	}

	return success;
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: UpdateIBuffer
// Desc: Attempts to update an index buffer
//--------------------------------------------------------------------------------------------------
bool CREBreakableGlassBuffer::UpdateIBuffer(const uint32 id, uint16* pIndData, const uint32 indCount, const uint32 offset, const bool isFrag)
{
	const SBuffer& buffer = GetBuffer(isFrag);
	bool success = false;

	if (indCount > 0 && buffer.pIndices && pIndData && RT_IsBufferValid(id, isFrag))
	{
		const int32 devID = buffer.pIndices->m_VS.m_nDevID;
		CRY_ASSERT_MESSAGE(devID >= 0, "Updating an invalid VBuffer.");
	  
		const uint32 stride = sizeof(uint16);
		gRenDev->m_DevBufMan.UpdateIB(devID, pIndData, indCount * stride, offset * stride);

		success = true;
	}

	return success;
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: UpdateTBuffer
// Desc: Attempts to update a tangent buffer
//--------------------------------------------------------------------------------------------------
bool CREBreakableGlassBuffer::UpdateTBuffer(const uint32 id, SPipTangents* pTanData, const uint32 tanCount, const uint32 offset, const bool isFrag)
{
	const SBuffer& buffer = GetBuffer(isFrag);
	bool success = false;

	if (tanCount > 0 && buffer.tangentStreamID != InvalidStream && pTanData && RT_IsBufferValid(id, isFrag))
	{
		const uint32 stride = sizeof(SPipTangents);
		gRenDev->m_DevBufMan.UpdateVB(buffer.tangentStreamID, pTanData, tanCount * stride, offset * stride);

		success = true;
	}

	return success;
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: DrawBuffer
// Desc: Draws a buffer slot
//--------------------------------------------------------------------------------------------------
void CREBreakableGlassBuffer::DrawBuffer(const uint32 count, const uint32 offset, const uint32 cyclicId, const bool isFrag)
{
	CRY_ASSERT_MESSAGE(count % 3 == 0, "Invalid number of glass indices");
	const SVertexConst& bufferConsts = s_bufferConsts[isFrag ? EBufferType_Loose : EBufferType_Solid];
	const SBuffer& buffer = GetBuffer(isFrag);

	if (IsVBufferValid(buffer))
	{
		CD3D9Renderer* pRenderer = gcpRendD3D;
		int vbOffset = 0, tbOffset = 0, ibOffset = 0;

		// Get buffers
		D3DVertexBuffer* pVBuffer = pRenderer->m_DevBufMan.GetD3DVB(buffer.pVertices->m_VS.m_nDevID, &vbOffset);
		D3DVertexBuffer* pTBuffer = pRenderer->m_DevBufMan.GetD3DVB(buffer.tangentStreamID, &tbOffset);
		D3DIndexBuffer* pIBuffer = pRenderer->m_DevBufMan.GetD3DIB(buffer.pIndices->m_VS.m_nDevID, &ibOffset);

		if (pVBuffer && pTBuffer && pIBuffer)
		{
			const int coreVStream = 0;
			const int tanVStream = 1;
			
			ibOffset = (ibOffset >> 1) + offset;

			// Bind data
			pRenderer->FX_SetVertexDeclaration(1 << VSF_TANGENTS, eVF_P3F_C4B_T2F);
			pRenderer->FX_SetVStream(coreVStream, pVBuffer, vbOffset, sizeof(SVF_P3F_C4B_T2F));
			pRenderer->FX_SetVStream(tanVStream, pTBuffer, tbOffset, sizeof(SPipTangents));
			pRenderer->FX_SetIStream(pIBuffer);

			// Draw
			pRenderer->FX_Commit();
			pRenderer->FX_DrawIndexedPrimitive(eptTriangleList, 0, bufferConsts.vbStride * cyclicId, bufferConsts.vbStride, ibOffset, count);
		}
	}
}//-------------------------------------------------------------------------------------------------